package calculator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import presentation.Printer;
import bean.Card;
import bean.Hand;
import bean.Table;

public class BridgeCalc {

	private DynamicStorer ds = new DynamicStorer();
	private static final int DS_MAX_STORE_SIZE = 5;
	private double branchingFactor = 0;
	private long nodeCount = 0;
	private CardComparator cc = new CardComparator();

	/**
	 * 
	 * @param fourCards
	 *            , position 0=north, 1=east, 2=south, 3=west
	 * @param trump
	 * @param lead
	 * @return winner in 0,1,2,3
	 */
	public static int resolveWinner(Map<Integer, Card> fourCards, int trump, int lead) {
		int firstSuit = fourCards.get(lead).getSuit();
		int winner = lead;
		// no trump
		if (trump == Card.NOTRUMP || firstSuit == trump) {
			for (int i = 0; i < 4; i++) {
				Card c = fourCards.get(i);
				if (c.getSuit() == firstSuit && c.getRank() > fourCards.get(winner).getRank())
					winner = i;
			}
			// trump
		} else {
			for (int i = 0; i < 4; i++) {
				Card c = fourCards.get(i);
				if (c.getSuit() == trump && fourCards.get(winner).getSuit() != trump)
					winner = i;
				else if (c.getSuit() == fourCards.get(winner).getSuit() && c.getRank() > fourCards.get(winner).getRank())
					winner = i;
			}
		}
		return winner;
	}

	public int calc(Table t) throws Exception {
		cc.setTrump(t.getTrump());
		int result = calcRecur(t, -1, 14);
		Printer.println("branching Factor = " + branchingFactor);
		return result;
	}

	/**
	 * return the double dummy NS tricks
	 * 
	 * @param t
	 * @param max
	 * @param min
	 * @return
	 * @throws Exception
	 */
	private int calcRecur(Table t, int max, int min) throws Exception {

		int result = 0;
		// base case
		if (t.getHandSize() == 0) {
			result = 0;
			return result;
		} else if (t.getHandSize() == 1) {
			Short trick = ds.getResult(t);
			if (trick == null) {
				HashMap<Integer, Card> fourCards = new HashMap<Integer, Card>(4);
				int i = 0;
				for (Hand hand : t.getHands()) {
					fourCards.put(i++, hand.getCards().get(0));
				}
				int winner = resolveWinner(fourCards, t.getTrump(), t.getLead());
				if (winner == 0 || winner == 2) {
					trick = 1;
				} else {
					trick = 0;
				}
				ds.storeTableResult(t, trick);
			}
			result = trick;
			return result;

		} else {
			if (t.getTurn() == t.getLead()) {
				Short trick = ds.getResult(t);
				if (trick != null) {
					return trick.intValue();
				}
			}

			// recursion cases
			// max tricks if turn is N/S
			if (t.getTurn() == 0 || t.getTurn() == 2) {
				ArrayList<Card> cardChoice;
				if (t.getLead() == t.getTurn()) {
					cardChoice = t.getHand(t.getTurn()).getAvailableCards(Card.NOTRUMP);
				} else {
					cardChoice = t.getHand(t.getTurn()).getAvailableCards(t.getLeadSuit());
				}
				smartOrderCard(cardChoice, t);
				// select max from the card choice
				int bestCardTrick = 0;
				int i = 0;
				for (Card c : cardChoice) {
					Table resultTable = getResultTable(t, c);
					int roundModifier = 0;
					if (resultTable.getCardOnTable().size() == 4) {
						int winner = resolveWinner(resultTable.getCardOnTable(), t.getTrump(), t.getLead());
						roundModifier = ((winner % 2) ^ 1);
						resultTable.clearCardOnTable(winner);
					}
					int nodeResult = calcRecur(resultTable, bestCardTrick, min); // 0 to
					if (bestCardTrick < nodeResult + roundModifier)
						bestCardTrick = nodeResult + roundModifier;
					i++;
					if (min <= bestCardTrick || bestCardTrick == t.getHandSize()) {
						break;
					}

				}
				branchingFactor = (branchingFactor / (nodeCount + 1) * nodeCount) + ((double) i / (nodeCount + 1));
				nodeCount++;
				result = bestCardTrick;
			} else {
				ArrayList<Card> cardChoice;
				if (t.getLead() == t.getTurn()) {
					cardChoice = t.getHand(t.getTurn()).getAvailableCards(Card.NOTRUMP);
				} else {
					cardChoice = t.getHand(t.getTurn()).getAvailableCards(t.getLeadSuit());
				}
				smartOrderCard(cardChoice, t);
				// select max from the card choice
				int minCardTrick = 999;
				int i = 0;
				for (Card c : cardChoice) {
					Table resultTable = getResultTable(t, c);
					int roundModifier = 0;
					if (resultTable.getCardOnTable().size() == 4) {
						int winner = resolveWinner(resultTable.getCardOnTable(), t.getTrump(), t.getLead());
						roundModifier = ((winner % 2) ^ 1);
						resultTable.clearCardOnTable(winner);
					}
					int nodeResult = calcRecur(resultTable, max, minCardTrick);

					if (minCardTrick > nodeResult + roundModifier)
						minCardTrick = nodeResult + roundModifier;
					i++;
					if (minCardTrick == 0 || minCardTrick <= max) {
						break;
					}
				}
				branchingFactor = (branchingFactor / (nodeCount + 1) * nodeCount) + ((double) i / (nodeCount + 1));
				nodeCount++;
				result = minCardTrick;
			}
			if ((t.getHandSize() <= DS_MAX_STORE_SIZE) && t.getTurn() == t.getLead() && ds.getResult(t) == null) {
				// ds.storeTableResult(t, result);
				if (ds.getSize() % 50000 == 0)
					Printer.println(String.valueOf(ds.getSize()));
			}
			if (t.getHandSize() >= 9 && t.getTurn() == t.getLead()) {
				// Printer.printTable(t);
				// Printer.println("result ="+result);
				Printer.println("Done branch:" + t.getHandSize() + "," + branchingFactor);
			}
		}
		return result;
	}

	private Table getResultTable(Table t, Card card) throws Exception {
		Table resultingTable = t.clone();
		resultingTable.addCardOnTable(t.getTurn(), card);
		boolean removeOK = resultingTable.getHand(t.getTurn()).playCard(card);
		if (!removeOK) {
			throw new Exception("Wrong Card");
		}
		resultingTable.setTurn((t.getTurn() + 1) % 4);

		return resultingTable;
	}

	private void smartOrderCard(List<Card> cardChoice, Table t) {
		Card winningCard = t.getWinningCard();
		cc.setSurrender(false);
		// if partner already winning, surrender the tricks
		if (winningCard != null) {
			for (int i = 0; i < 4; i++) {
				if (winningCard.equals(t.getCardOnTable().get(i))) {
					if (i % 2 == t.getTurn() % 2) {
						cc.setSurrender(true);
					}
				}
			}
		}
		cc.setIndexCard(winningCard);
		Collections.sort(cardChoice, cc);

	}
}
